/*

  Brush.m -- Pressure sensitive paint brush

  by Peter Graffagnino, NeXT Computer Inc.

  Brush's responsibilities are

  - implement brushMoveTo:... and brushLineTo:... painting protocol.
  - provide target/action methods to tweak brush parameters

  You may freely copy, distribute, and reuse the code in this example.
  NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  fitness for any particular use.

*/


#import <appkit/appkit.h>
#import <math.h>
#import "Brush.h"

@implementation Brush



/*
 * fill_tangent_trapezoid:  takes two circles of radius r1 and r2 whose centers
 * are separated by (dx,dy) and draws the trapezoid formed by the common
 * tangents of the two circles, and parallel chords of the two circles.  This
 * is essentially that shape of a belt around two different sized pullies.  The
 * code assumes that the current point is at the center of the first circle
 */ 

void fill_tangent_trapezoid(double r1, double r2, double dx, double dy)
{
    double d, eta, nu, x1, y1, x1m, y1m, x2, y2, x2m, y2m;
    double xeta, yeta, xnu, ynu;

    d = sqrt(dx * dx + dy * dy);

    /*
     * protect against the degenerate case (one circle contained in the other)
     */
    if ((r1 - r2) >= d || (r1 - r2) <= -d)
	return;

    eta = (r1 - r2)/d;
    nu = sqrt(1 - eta*eta);
    
    nu /= d;     xnu = dx*nu;      ynu = dy*nu;
    eta /= d;    xeta = dx*eta;    yeta = dy*eta;
    
    x1  = xeta - ynu;    x2 = dx + r2*x1;      x1 *= r1;
    y1  = yeta + xnu;    y2 = dy + r2*y1;      y1 *= r1;
    
    x1m = xeta + ynu;   x2m = dx + r2*x1m;    x1m *= r1;
    y1m = yeta - xnu;   y2m = dy + r2*y1m;    y1m *= r1;
    
    PSrmoveto(x1,y1);
    PSrlineto(x1m - x1, y1m - y1);
    PSrlineto(x2m - x1m, y2m - y1m);
    PSrlineto(x2 - x2m, y2 - y2m);
    PSclosepath();
    PSfill();
}


/*
 * brushMoveTo:... and brushLineTo:... are the basic painting protocol.  The
 * assumption is that we are currently lockFocused: appropriately.  Our
 * only job is to emit postscript to draw the brush stroke, and return a dirty
 * rectangle in which we drew.
 */


- brushMoveTo:(float)x :(float)y withPressure:(float) pressure
 dirtyRect: (NXRect *) dirty
{
    /*
     * latch the current point as the last point.  lastsize is immaterial
     * since the line will be zero length.  This allows us to use lineto
     * to plot a single point.
     */
    lastx = x; lasty = y; lastsize = 1.0;
    return [self brushLineTo: x : y withPressure: pressure dirtyRect: dirty];
}
    

- brushLineTo:(float)x :(float)y withPressure:(float) pressure
 dirtyRect: (NXRect *) dirty
{
    float size;

    NXSetColor(brushColor);	

    size = pressureCoefficient*pow(pressure,pressureExponent) + minSize;

    /* Do the endpoint first */
    PSsetlinewidth(size);
    PSsetlinecap(1);
    
    PSmoveto(x,y); PSclosepath(); PSstroke();

    /* Connect the last circle to this one with bimodular tangents (honest) */
    PSmoveto(lastx,lasty);
    fill_tangent_trapezoid(lastsize/2, size/2, x - lastx, y - lasty);

    /* calculate dirty rect for flush */
    if(lastx > x) {
	dirty->origin.x = x;
	dirty->size.width = lastx - x;
    } else {
	dirty->origin.x = lastx;
	dirty->size.width = x - lastx;
    }
    if (lasty > y) {
	dirty->origin.y = y;
	dirty->size.height = lasty - y;
    } else {
	dirty->origin.y = lasty;
	dirty->size.height = y - lasty;
    }
    NXInsetRect(dirty, -(size + lastsize + 2.0)/2.0,
		-(size + lastsize + 2.0)/2.0);
    lastx = x; lasty = y; lastsize = size;
    return self;
}


/*
 * Target/action parameter methods.  We implement a number of action methods
 * to allow control over our various parameters.  We also use these methods
 * to latch initial values from nibified controls, by declaring appropriately
 * named phantom outlets in nib.
 */

- setMinSize: sender
{
    minSize = [sender floatValue];
    return self;
}

- setPressureExponent: sender
{
    pressureExponent = [sender floatValue];
    return self;
}

- setPressureCoefficient: sender
{
    pressureCoefficient = [sender floatValue];
    return self;
}

- setBrushColor: sender
{

    brushColor = [sender color];
    return self;
}

/*
 * init -- give some reasonable default values.  Note that the above
 * methods are called by the outlet-setting pass of nib loading, making
 * the real defaults come from the nib itself
 */

- init
{
    self = [super init];
    brushColor = NX_COLORBLACK;
    pressureExponent = 2.0;
    minSize = 0.0;
    pressureCoefficient = 12.0;

    return self;
}
    
